/**
 * 
 */
package jmine.tec.utils.cripto;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import jmine.tec.utils.cripto.exception.CriptoException;

/**
 * Esta classe serve de utilitário para encriptar e decriptar mensagens usando o algorítmo RSA.<br>
 * 
 * @author gigante
 */
public final class RSACriptoUtil {

    /**
     * C'tor
     */
    private RSACriptoUtil() {
    }

    /**
     * Este método encripta uma mensagem passada em bytes.<br>
     * A mensagem deve respeitar o tamanho máximo permitido pela chave passada.<br>
     * A chave passada de respeitar a especificação de uma chave RSA.<br>
     * O algorítmo usado para encriptar a mensagem é o algorítmo de encriptação RSA e pode ocorrer erro de 'padding'. O erro de 'padding'
     * ocorre quando a mensagem passada está em um formato inválido para ser feito o offset do algorítmo.<br>
     * Por exemplo, uma chave gerada com tamanho de 1024, conseguirá encriptar até 117 bytes.
     * 
     * @param chave a chave RSA gerada com um determinado tamanho.
     * @param mensagem a mensagem que queremos encriptar para a chave passada
     * @return o array com os bytes encriptados.
     */
    public static byte[] encripta(final Key chave, final byte[] mensagem) {

        if (mensagem == null) {
            throw new CriptoException("Não é possível encriptar uma mensagem nula.");
        }

        Cipher cipher = createCipher();

        try {
            cipher.init(Cipher.ENCRYPT_MODE, chave);
        } catch (InvalidKeyException e) {
            throw new CriptoException("Chave passada inválida.");
        }

        try {
            return cipher.doFinal(mensagem);
        } catch (IllegalBlockSizeException e) {
            throw new CriptoException("Tamanho da mensagem inválida: " + e.getLocalizedMessage(), e);
        } catch (BadPaddingException e) {
            throw new CriptoException("Erro ao utilizar padding.", e);
        }
    }

    /**
     * Cria um novo cipher
     * 
     * @return {@link Cipher}
     */
    private static Cipher createCipher() {
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new CriptoException("Erro inesperado: Cipher não conhece o algorítmo RSA.", e);
        } catch (NoSuchPaddingException e) {
            throw new CriptoException("Erro inesperado: erro ao utilizar padding.", e);
        }
        return cipher;
    }

    /**
     * Este método decripta um array de bytes passado.<br>
     * O array passado deve ter sido gerado a partir da chave privada que foi gerada no mesmo par da chave pública aqui passada.<br>
     * A chave passada de respeitar a especificação de uma chave RSA.<br>
     * O algorítmo usado para decriptar a mensagem é o algorítmo de encriptação RSA e pode ocorrer erro de 'padding'. O erro de 'padding'
     * ocorre quando a mensagem passada está em um formato inválido para ser feito o offset do algorítmo. Para a encriptação dar certo, o
     * algorítmo de encriptaççao usado deve ter sido o RSA também.<br>
     * Caso o método não consiga decriptar corretamente o array, não necessariamente ele lançará uma exceção. O que acontecerá neste caso é
     * a geração de uma mensagem que simplesmente não fará sentido.
     * 
     * @param chave a chave RSA pública gerada junto com a chave privada usada para encriptar a mensagem.
     * @param mensagemCriptografada o array de bytes que foi encriptado posteriormente.
     * @return o array de bytes com a mensagem decriptada. // *
     */
    public static byte[] decripta(final Key chave, final byte[] mensagemCriptografada) {

        if (mensagemCriptografada == null) {
            throw new CriptoException("Não é possível decriptar dados nulos.");
        }

        Cipher cipher = createCipher();

        try {
            cipher.init(Cipher.DECRYPT_MODE, chave);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new CriptoException("Chave passada inválida.");
        }

        try {
            return cipher.doFinal(mensagemCriptografada);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
            throw new CriptoException("Tamanho dos dados inválido: " + e.getLocalizedMessage());
        } catch (BadPaddingException e) {
            e.printStackTrace();
            throw new CriptoException("Erro ao utilizar padding.");
        }
    }
}
